﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using Xoohoo.Extensions;
using Xoohoo.Infrastructure.FastReflectionLib;
using System.Web.Routing;
using System.Text;

namespace Xoohoo.Html
{
    public static class InputExtensions
    {
        #region Static Constructor

        private static IMethodInvoker s_TextBoxHelperInvoker;
        private static IMethodInvoker s_PasswordHelperInvoker;
        private static IMethodInvoker s_CheckBoxHelperInvoker; 
       
        static InputExtensions()
        {
            MethodInfo textBoxHelperMethodInfo = typeof(System.Web.Mvc.Html.InputExtensions).GetMethod("TextBoxHelper", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            s_TextBoxHelperInvoker = new MethodInvoker(textBoxHelperMethodInfo);

            MethodInfo passwordHelperMethodInfo = typeof(System.Web.Mvc.Html.InputExtensions).GetMethod("PasswordHelper", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            s_PasswordHelperInvoker = new MethodInvoker(passwordHelperMethodInfo);

            MethodInfo checkBoxHelperMethodInfo = typeof(System.Web.Mvc.Html.InputExtensions).GetMethod("CheckBoxHelper", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            s_CheckBoxHelperInvoker = new MethodInvoker(checkBoxHelperMethodInfo);
        }

        #endregion

        #region TextBox

        public static MvcHtmlString TextBoxFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression)
        {
            return htmlHelper.TextBoxFor(model, expression, null);
        }

        public static MvcHtmlString TextBoxFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , object htmlAttributes)
        {
            return htmlHelper.TextBoxFor(model, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        public static MvcHtmlString TextBoxFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model, Expression<Func<TModel, TProperty>> expression
            , IDictionary<string, object> htmlAttributes)
        {
            ViewDataDictionary<TModel> viewData = new ViewDataDictionary<TModel>(model);
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, viewData);

            return (MvcHtmlString)s_TextBoxHelperInvoker.Invoke(
                null
                , htmlHelper
                , metadata
                , metadata.Model
                , ExpressionHelper.GetExpressionText(expression)
                , htmlAttributes);
        }


        #endregion

        #region Password

        public static MvcHtmlString PasswordFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression)
        {
            return htmlHelper.PasswordFor(model, expression, null);
        }

        public static MvcHtmlString PasswordFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {
            return htmlHelper.PasswordFor(model, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        public static MvcHtmlString PasswordFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IDictionary<string, object> htmlAttributes)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }

            ViewDataDictionary<TModel> viewData = new ViewDataDictionary<TModel>(model);

            return (MvcHtmlString)s_PasswordHelperInvoker.Invoke(
                null
                , htmlHelper
                , ModelMetadata.FromLambdaExpression(expression, viewData)
                , ExpressionHelper.GetExpressionText(expression)
                , null
                , htmlAttributes);
        }

        #endregion

        #region CheckBox

        public static MvcHtmlString CheckBoxFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression)
        {
            return htmlHelper.CheckBoxFor(model, expression, null);
        }

        public static MvcHtmlString CheckBoxFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , object htmlAttributes)
        {
            return htmlHelper.CheckBoxFor(model, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        public static MvcHtmlString CheckBoxFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IDictionary<string, object> htmlAttributes)
        {
            ViewDataDictionary<TModel> viewData = new ViewDataDictionary<TModel>(model);
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, viewData);
            bool? isChecked = null;
            if (metadata.Model != null)
            {
                bool modelChecked;
                if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
                {
                    isChecked = modelChecked;
                }
            } 
            return (MvcHtmlString)s_CheckBoxHelperInvoker.Invoke(
                null
                , htmlHelper
                , metadata
                , ExpressionHelper.GetExpressionText(expression)
                , isChecked
                , htmlAttributes);
        }

        #endregion

        #region CheckBoxWithValue

        public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, string value, bool isChecked)
        {
            return CheckBox(htmlHelper, name, value, isChecked, true);
        }

        public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, string value, bool isChecked, bool enabled)
        {
            return CheckBox(htmlHelper, name, value, isChecked, enabled, null);
        }

        public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, string value, bool isChecked, object htmlAttributes)
        {
            return CheckBox(htmlHelper, name, value, isChecked, true, htmlAttributes);
        }

        public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, string value, bool isChecked, bool enabled, object htmlAttributes)
        {
            RouteValueDictionary attributes = new RouteValueDictionary(htmlAttributes);

            if (!enabled)
                attributes.Add("disabled", "disabled");

            return value != null ? CheckBoxWithValue(name, value, isChecked, attributes) : htmlHelper.CheckBox(name, isChecked, attributes);

        }
 
        public static MvcHtmlString CheckBoxWithValue(string name, string value, bool isChecked, IDictionary<string, object> htmlAttributes)
        {
            return MvcHtmlString.Create(string.Format("<input id=\"{0}_{1}\" name=\"{0}\" value=\"{1}\" {2} type=\"checkbox\" {3}/>", name, value, isChecked ? "checked=\"checked\"" : String.Empty, htmlAttributes.ToAttributeList()));
        }
 
        public static MvcHtmlString CheckBoxList(this HtmlHelper htmlHelper, string name, MultiSelectList items, bool enabled, string controlTextSplit, string itemSplit, object htmlAttributes)
        {
            if (controlTextSplit.IsNullOrEmpty()) controlTextSplit = "&nbsp;";
            StringBuilder sb = new StringBuilder();
            foreach (var item in items)
            {
                sb.Append(CheckBox(htmlHelper, name, item.Value, item.Selected, enabled, htmlAttributes).ToString() + controlTextSplit + item.Text);
                if (!itemSplit.IsNullOrEmpty())
                    sb.Append(itemSplit);
            }
            return MvcHtmlString.Create(sb.ToString());
        }
 
        private static MvcHtmlString ToAttributeList(this IDictionary<string, object> htmlAttributes)
        {
            StringBuilder sb = new StringBuilder();
            if (htmlAttributes != null)
            {
                string resultFormat = " {0}=\"{1}\"";
                foreach (var a in htmlAttributes)
                {
                    sb.AppendFormat(resultFormat, a.Key, a.Value);
                }
            }
            return MvcHtmlString.Create(sb.ToString());

        }

        #endregion

        #region CheckBoxList

        public static MvcHtmlString CheckBoxListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, IEnumerable<TProperty>>> expression
            , IEnumerable<SelectListItem> selectList
            )
        {
            return htmlHelper.CheckBoxListFor(model, expression, selectList, null);
        }

        public static MvcHtmlString CheckBoxListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, IEnumerable<TProperty>>> expression
            , IEnumerable<SelectListItem> selectList
            , object htmlAttributes
            )
        {
            return htmlHelper.CheckBoxListFor(model, expression, selectList, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        public static MvcHtmlString CheckBoxListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, IEnumerable<TProperty>>> expression
            , IEnumerable<SelectListItem> selectList
            , IDictionary<string, object> htmlAttributes
            )
        {
            if (selectList == null)
                return MvcHtmlString.Empty;

            string propertyName = ExpressionHelper.GetExpressionText(expression);
            string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName);

            IEnumerable<TProperty> selectedValues;
            if (model != null)
                selectedValues = expression.Compile().Invoke(model);
            else
                selectedValues = Enumerable.Empty<TProperty>();

            TagBuilder divTag = new TagBuilder("div");
            divTag.MergeAttributes(htmlAttributes, true);
            
            StringBuilder listItems = new StringBuilder(selectList.Count());
            foreach (SelectListItem item in selectList)
            {
                listItems.AppendFormat("<div>"
                    +"<input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" value=\"{1}\" {2} />"
                    +"<label for=\"{0}_{1}\">{3}</label>"
                    +"</div>",
                    fullHtmlFieldName,
                    item.Value,
                    selectedValues.Any(i=>i.ToString()==item.Value) ? "checked=\"checked\"" : String.Empty,
                    item.Text);
            }
            divTag.InnerHtml = listItems.ToString();
            return MvcHtmlString.Create(divTag.ToString(TagRenderMode.Normal));
        }
        
        #endregion

        #region RadioButtonList

        public static MvcHtmlString RadioButtonListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            )
        {
            return htmlHelper.RadioButtonListFor(model, expression, selectList,null, null);
        }

        public static MvcHtmlString RadioButtonListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , object defaultValue
            )
        {
            return htmlHelper.RadioButtonListFor(model, expression, selectList, defaultValue, null);
        }

        public static MvcHtmlString RadioButtonListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , object defaultValue
            , object htmlAttributes
            )
        {
            return htmlHelper.RadioButtonListFor(model, expression, selectList, defaultValue, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        public static MvcHtmlString RadioButtonListFor<TViewModel, TModel, TProperty>(this HtmlHelper<TViewModel> htmlHelper
            , TModel model
            , Expression<Func<TModel, TProperty>> expression
            , IEnumerable<SelectListItem> selectList
            , object defaultValue
            , IDictionary<string, object> htmlAttributes
            )
        {
            if (selectList == null)
                return MvcHtmlString.Empty;
            //属性名称
            string propertyName = ExpressionHelper.GetExpressionText(expression);
            string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName);
            
            //获取属性值
            ViewDataDictionary<TModel> viewData = new ViewDataDictionary<TModel>(model);
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, viewData);
            string propertyValue = metadata.Model != null?metadata.Model.ToString():null;

            TagBuilder divTag = new TagBuilder("div");
            divTag.MergeAttributes(htmlAttributes, true);
            StringBuilder listItems = new StringBuilder(selectList.Count());
            foreach (SelectListItem item in selectList)
            {
                listItems.AppendFormat("<div>"
                    +"<input type=\"radio\" name=\"{0}\" id=\"{0}_{1}\" value=\"{1}\" {2} />"
                    +"<label for=\"{0}_{1}\">{3}</label>"
                    +"</div>",
                    fullHtmlFieldName,
                    item.Value,
                    RadioIsChecked(propertyValue,defaultValue,item) ? "checked=\"checked\"" : String.Empty,
                    item.Text);
            }
            divTag.InnerHtml = listItems.ToString();
            return MvcHtmlString.Create(divTag.ToString(TagRenderMode.Normal));
        }

        private static bool RadioIsChecked(string propertyValue, object defaultValue, SelectListItem selectListItem)
        {
            if (propertyValue != null)
                return propertyValue == selectListItem.Value;
            else if(defaultValue!=null)
                return propertyValue == defaultValue.ToString();
            else if (selectListItem.Selected)
                return true;
            else
                return false;
        }

        #endregion

    }
}
